<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>5.模拟光照让3D场景更逼真-聚光灯效果</title>
    <style>
        canvas{
            border:1px solid #aaa;
            margin: 0 auto;
            display: block;
        }
    </style>
</head>
<body>
    <canvas width="512" height="512"></canvas>

    <script type="module">
        import { Renderer, Camera, Transform, Program, Color, Mesh, Texture, Orbit, Sphere, Box, Cylinder, Torus } from '/common/lib/ogl/index.mjs';
        import * as dat from '/common/lib/dat.gui.js';

        let canvas = document.querySelector("canvas");

        // 1. 创建画布宽高512的renderer对象
        const renderer = new Renderer({
            canvas,
            width:512,
            height:512,
        });

        const gl = renderer.gl;
        gl.clearColor(1, 1, 1, 1);

        (async function(){
            // 2. 创建相机（默认透视投影）
            // 视角35°
            const camera = new Camera(gl, { fov: 35 });
            // 相机的位置
            camera.position.set(0, 1, 7);
            // 相机的朝向
            camera.lookAt([0, 0, 0]);

            // 3. 创建场景
            const scene = new Transform();

            // 4. 创建几何体对象
            const sphereGeometry = new Sphere(gl);
            const cubeGeometry = new Box(gl);
            const cylinderGeometry = new Cylinder(gl);
            const torusGeometry = new Torus(gl);
                    
            // 5. 创建webgl程序
            const vertex = /* glsl */ `
            precision highp float;
            
            attribute vec3 position;
            attribute vec3 normal;
            attribute vec2 uv;
            
            uniform mat4 modelViewMatrix;
            uniform mat4 projectionMatrix;
            uniform mat3 normalMatrix;
            
            varying vec3 vNormal;
            varying vec4 vPos;
            
            void main() {
                vPos = modelViewMatrix * vec4(position, 1.0);
                // 计算法向量
                vNormal = normalize(normalMatrix * normal);
                gl_Position = projectionMatrix * vPos;
            }
            `;
            
            const fragment = /* glsl */ `
                precision highp float;

                uniform mat4 viewMatrix;
                uniform vec3 ambientLight;
                uniform vec3 materialReflection;

                // 聚光灯的颜色强度
                uniform vec3 spotLightColor;
                // 聚光灯的位置
                uniform vec3 spotLightPosition;
                // 光照的衰减系数
                uniform vec3 spotLightDecayFactor;
                // 点光源的照射方向
                uniform vec3 spotLightDirection;
                // 点光源的照射角度
                uniform float spotLightAngle;

                varying vec3 vNormal;
                varying vec4 vPos;

                void main() {
                    // 点光源到物体表面的点坐标的方向
                    vec3 invLight = (viewMatrix * vec4(spotLightPosition, 1.0)).xyz - vPos.xyz;
                    vec3 invNormal = normalize(invLight);

                    // 光线到点坐标的距离，用来计算衰减 
                    float dis = length(invLight);

                    // 聚光灯的朝向 
                    vec3 dir = (viewMatrix * vec4(spotLightDirection, 0.0)).xyz;

                    // 通过余弦值判断夹角范围 
                    float ang = cos(spotLightAngle); 
                    // 计算聚光灯的朝向与点光源到物体表面的点坐标的方向的余弦值,比较两个夹角
                    float r = step(ang, dot(invNormal, normalize(-dir)));

                    // 计算衰减 
                    float decay = min(
                        1.0, 
                        1.0 / (spotLightDecayFactor.x * pow(dis, 2.0) + spotLightDecayFactor.y * dis + spotLightDecayFactor.z)
                    );

                    // 求光线与法线的夹角
                    float cos = max(dot(normalize(invNormal), vNormal), 0.0);

                    // 计算漫反射 
                    vec3 diffuse = r * decay * cos * spotLightColor; 

                    // 合成颜色
                    gl_FragColor.rgb = (ambientLight + diffuse) * materialReflection;
                    gl_FragColor.a = 1.0;
                }
            `;
            
            // 定义环境光的强度
            const ambientLight = {value: [0.5, 0.5, 0.5]};

            // 定义平行光的方向与强度
            const directional = {
            spotLightPosition: {value: [3, 3, 0]},
            spotLightColor: {value: [1, 1, 1]},
            spotLightDecayFactor: {value: [0.05, 0, 1]},
            spotLightDirection: {value: [-1, -1, 0]},
            spotLightAngle: {value: Math.PI / 12},
            };

            const program1 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ...directional,
                    ambientLight,
                    materialReflection: {value: [0, 0, 1]},
                },
            });

            const program2 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ...directional,
                    ambientLight,
                    materialReflection: { value: [1, 0, 1] },
                },
            });
            const program3 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ...directional,
                    ambientLight,
                    materialReflection: { value: [0, 0.5, 0] },
                },
            });
            const program4 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ...directional,
                    ambientLight,
                    materialReflection: { value: [1, 0, 0] },
                },
            });
            
            // 6. 创建网格对象
            const controls = new Orbit(camera);
            
            const torus = new Mesh(gl, {geometry: torusGeometry, program: program1});
            torus.position.set(0, 1.3, 0);
            torus.setParent(scene);

            const sphere = new Mesh(gl, {geometry: sphereGeometry, program: program2});
            sphere.position.set(1.3, 0, 0);
            sphere.setParent(scene);

            const cube = new Mesh(gl, {geometry: cubeGeometry, program: program3});
            cube.position.set(0, -1.3, 0);
            cube.setParent(scene);

            const cylinder = new Mesh(gl, {geometry: cylinderGeometry, program: program4});
            cylinder.position.set(-1.3, 0, 0);
            cylinder.setParent(scene);
            
            // 7. 渲染
            requestAnimationFrame(update);
            function update() {
                requestAnimationFrame(update);
                controls.update();

                // torus.rotation.y -= 0.02;
                // sphere.rotation.y -= 0.03;
                // cube.rotation.y -= 0.04;
                // cylinder.rotation.y -= 0.02;

                renderer.render({scene, camera});
            }

        })()
    </script>
</body>
</html>